package apibasej.infra;

import java.io.IOException;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.logging.Level;

import apibasej.basic.config.ConfigManager;
import apibasej.basic.misc.UtilTrace;
import apibasej.basic.pool.APIBasePoolException;
import apibasej.basic.thread.SimpleThreadPool;

public class SimpleServerSocket<T extends ExecSocket> extends ConfigManager{

	// para parecer com os status HTTP 200 e 500
	//public static final byte STATUS_OK = 2;
	public static final byte STATUS_ERROR_RESP_SOCKET = 5;
	
	private int port;
	private Class<T> classExecSocket; // deve ter construtor vazio
	
	private SimpleThreadPool threadPool;
	private int initialSizeThreadPool = 5;
	private int recommendedSize = 100;
	private int maxSizeThreadPool = 300;

	private boolean stopped = false;
	
	public SimpleServerSocket(int port, Class<T> classExecSocket) {
		this.port = port;
		this.classExecSocket = classExecSocket;
	}
	
	
	private ServerSocket serverSocket;
	public void start() throws IOException{
		if(serverSocket==null){
			stopped = false;
			serverSocket = new ServerSocket(getPort());
			threadPool = new SimpleThreadPool(getInitialSizeThreadPool(), getRecommendedSize(), getMaxSizeThreadPool());
			new Thread(){
				@Override
				public void run() {
					while(serverSocket!=null){ // seta null no stop()
						ExecSocket execSocket = null;
						Socket s = null;
						try {
							s = serverSocket.accept();
							// ver opção de receber flag para stop ...
							execSocket = classExecSocket.newInstance();
							execSocket.setSocket(s);
							execSocket.setSimpleServerSocket(getThis());
							threadPool.getItemPool().execute(execSocket); // deve tratar todos erros internamente
						} catch (IOException e) {
							//aqui não adianta retornar erro, pois o erro foi no accept, portanto não tem socket
							// só registra log se não tiver sido parado pelo método stop();
							if(!stopped) logDef(Level.SEVERE,"Error on accept socket",e);
						} catch (InstantiationException e) {
							String m = "Error on instantiate "+classExecSocket.getName();
							logDef(Level.SEVERE,m,e);
							tryReturnError(s, UtilTrace.getStackTrace(m, e));
						} catch (APIBasePoolException e) {
							String m = "Error on retrieve thread from pool: "+e.getMessage();
							logDef(Level.SEVERE,m,e);
							tryReturnError(s, UtilTrace.getStackTrace(m, e));
						} catch (Throwable e) {
							String m = "Error on process socket request: "+e.getMessage();
							logDef(Level.SEVERE,m,e);
							tryReturnError(s, UtilTrace.getStackTrace(m, e));
						}
						// não pode fechar pois a thread será executada depois ...
						//finally{ if(execSocket!=null) execSocket.close(); // já fecha o socket e os streams}
					}
				};
			}.start();
		}
	}
	
	// PADRÕES DE RETORNO
	// o PRIMEIRO byte (0) de resposta será o status
	
	
	// não garante o retorno, portanto deve-se tratar e registrar em log antes de chamar este método
	public void tryReturnError(Socket s, String textError){
		if(s!=null){
			OutputStream o = null;
			try{
				o = s.getOutputStream();
				// status 
				o.write(new byte[]{STATUS_ERROR_RESP_SOCKET});
				
				// não ter tamanho na resposta, este controle deve ser feito para cada implementação 
				// os próximos 4 bytes (1,2,3,4) serão transformados para int e devem ter o tamanho do corpo resposta (um int tem 4 bytes) 
				//byte[] sizeBody = BigInteger.valueOf(bodyErr.length).toByteArray(); 
				// completar os bytes com o size para 4 bytes 
				//if(sizeBody.length<4) o.write(new byte[4-sizeBody.length]); // irá criar um array para completar com zeros
				//o.write(sizeBody); 
				
				byte[] bodyErr = textError.getBytes(getProp(CHARSET));
				o.write(bodyErr);
				o.flush();
			}catch (Exception e) {
				logDef(Level.SEVERE,"Error on try return response error",e);
			}finally{ // FECHA O SOCKET E OS STREAMS, POIS JÁ É UMA RESPOSTA DE ERRO
				if(o!=null){
					try {
						 o.close();
					} catch (Exception e) {
						logDef(Level.SEVERE,"Error on close Socket OutputStream",e);
					}
				}
				if(s!=null && !s.isClosed()) {
					try {
						s.close();
					} catch (Exception e) {
						logDef(Level.SEVERE,"Error on close Socket",e);
					}
				}
			}
		}
	}
	
	private SimpleServerSocket<T> getThis(){
		return this;
	}
	
	public void stop(){
		if(serverSocket!=null){
			try {
				if(!serverSocket.isClosed()) serverSocket.close();
				stopped = true;
				serverSocket = null;
			} catch (IOException e) {
				logDef(Level.SEVERE,"Error on close ServerSocket",e);
			}
		}
		if(threadPool!=null){
			threadPool.invalidateAll();
			threadPool = null;
		}
	}
	
	@Override
	protected void finalize() throws Throwable {
		super.finalize();
		stop();
	}
	
	public int getPort() {
		return port;
	}

	public int getInitialSizeThreadPool() {
		return initialSizeThreadPool;
	}

	public int getMaxSizeThreadPool() {
		return maxSizeThreadPool;
	}

	public void setInitialSizeThreadPool(int initialSizeThreadPool) {
		this.initialSizeThreadPool = initialSizeThreadPool;
	}

	public void setMaxSizeThreadPool(int maxSizeThreadPool) {
		this.maxSizeThreadPool = maxSizeThreadPool;
	}
	
	public SimpleThreadPool getThreadPool() {
		return threadPool;
	}

	public int getRecommendedSize() {
		return recommendedSize;
	}

	public void setRecommendedSize(int recommendedSize) {
		this.recommendedSize = recommendedSize;
	}
	
}
